{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Pytorch构建神经网络（一）（1-13节）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.Pytorch介绍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.1 pytorch简介"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1.1.1 pytorch简介\n",
    "* pytorch 是深度学习框架和科学计算包\n",
    "* pytorch之所以可以进行科学计算是因为它是一个张量库并且有相关的张量运算\n",
    "* pytorch和numpy有很强的互操作性，原因：1.张量和数组具有相似性； 2.pytorch的torch.tensor对象是由numpy的ndarray创建的，它们共享内存；\n",
    "* pytorch 张量运算可在GPU上运行；"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1.1.2 pytorch简史\n",
    "* pytorch的前身是torch，torch是基于lua语言(该语言晦涩难懂且生态系统很小)；\n",
    "* pytorch的发起者是facebook的研究员Soumith Chintala"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1.1.3 pytorch 库\n",
    "* torch: 包含所有torch包和tensor库的顶级包\n",
    "* troch.nn: 是用于建立神经网络的包，它包含类和模块，如layer,weight和forward function\n",
    "* torch.autograd: 支持张量的导数运算，负责优化神经网络\n",
    "* torch.nn.function: 访问损失函数的API\n",
    "* torch.optim: 访问优化器 (SGD,ADAM...)\n",
    "* torch.utils: 包含数据集和数据加载器的实用程序类\n",
    "* torchvision: 访问流行的数据集、计算机视觉中的模型框架及图像转换\n",
    "* 所有深度学习框架都有两个特性:张量库和计算导数的包(在pytorch中是torch和torch.autograd)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1.1.4 pytorch的特点\n",
    "* pytorch is modern, pythonic and thin design\n",
    "* pytorch is a python extension\n",
    "* pytorch的简单性使其具有更长的寿命\n",
    "* pytorch是研究中首选框架的原因：pytorch的计算图是动态的，而其他框架通常是静态的，许多深度学习领域的前沿研究都需要动态图或从动态图中获益\n",
    "* 计算图是用于描绘神经网络中张量的函数操作，通常用于计算优化神经网络权重所需的导数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.2 Pytorch的安装"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1.2.1 pytorch安装\n",
    "* step1: 下载并安装anaconda（https://www.anaconda.com/distribution/）\n",
    "* step2：进入pytorch网站（https://pytorch.org/）, 选择相应的配置，会出现相应的命令行，复制在终端并运行，即可安装；\n",
    "* 不需要自己额外安装Cuda，安装pytorch时Cuda会自动安装；\n",
    "* Cudnn是专门用于神经网络的包"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1.2.2 pytorch安装验证"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch         # 导入pytorch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.0.1\n"
     ]
    }
   ],
   "source": [
    "print(torch.__version__)   #打印pytorch的版本"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function torch.cuda.is_available>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "torch.cuda.is_available         #判断Cuda在我们的系统上是否可用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "None\n"
     ]
    }
   ],
   "source": [
    "print(torch.version.cuda)   #看cuda的版本  \n",
    "                            # 我的笔记本上没有安装gpu版本的pytorch；若有安装会显示出chuda的版本"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.3 CUDA简介"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1.3.1 GPU的运算\n",
    "* 最适合GPU的运算是可以并行完成的运算\n",
    "* 并行计算：一个特定的计算被分解成独立的可以同时进行的很小的计算，得到的计算被重新组合和同步，以形成原始较大的计算结果\n",
    "* 一个更大的计算可以被分解的任务的数量取决于硬件上的核数（CPU通常是4或16核，而GPU可以有成千上万个核，故GPU可以大大提升运算速度）\n",
    "* 核是指在给定处理器中进行计算的单元\n",
    "* 并行计算是使用GPU来完成的 => 最适合GPU的任务是可以并行完成的任务\n",
    "* 为什么GPU在深度学习中能够如此广泛的使用？——答：因为神经网络是易并行的(embarrassing parallel),即：很容易就能够将任务分解成一组独立的小任务；神经网络的很多计算都可以很容易地分解成更小的相互独立的计算，这使得GPU在深度学习任务中非常有用；\n",
    "* 一个卷积核对一张图像进行卷积的每个运算是独立且相继发生的，故可将其分成一个个小任务，使用GPU加速运算；\n",
    " (图像分块后送入神经网络是否同理？)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1.3.2 Cudnn\n",
    "* 用于神经网络的包\n",
    "* 在安装pytorch时已自带"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1.3.3 using Cuda with pytorch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([1., 2., 3.])"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 定义张量t，默认在CPU上； 再将其放在GPU上进行操作\n",
    "import torch\n",
    "import numpy as np\n",
    "\n",
    "t = torch.Tensor([1,2,3])\n",
    "t"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "ename": "AssertionError",
     "evalue": "Torch not compiled with CUDA enabled",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mAssertionError\u001b[0m                            Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-9-99dd62b9f3fb>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mt\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcuda\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m      2\u001b[0m \u001b[0mt\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m~\\Anaconda3\\lib\\site-packages\\torch\\cuda\\__init__.py\u001b[0m in \u001b[0;36m_lazy_init\u001b[1;34m()\u001b[0m\n\u001b[0;32m    159\u001b[0m         raise RuntimeError(\n\u001b[0;32m    160\u001b[0m             \"Cannot re-initialize CUDA in forked subprocess. \" + msg)\n\u001b[1;32m--> 161\u001b[1;33m     \u001b[0m_check_driver\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    162\u001b[0m     \u001b[0mtorch\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_C\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_cuda_init\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    163\u001b[0m     \u001b[0m_cudart\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0m_load_cudart\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m~\\Anaconda3\\lib\\site-packages\\torch\\cuda\\__init__.py\u001b[0m in \u001b[0;36m_check_driver\u001b[1;34m()\u001b[0m\n\u001b[0;32m     73\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m_check_driver\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     74\u001b[0m     \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtorch\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_C\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'_cuda_isDriverSufficient'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 75\u001b[1;33m         \u001b[1;32mraise\u001b[0m \u001b[0mAssertionError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Torch not compiled with CUDA enabled\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     76\u001b[0m     \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mtorch\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_C\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_cuda_isDriverSufficient\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     77\u001b[0m         \u001b[1;32mif\u001b[0m \u001b[0mtorch\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_C\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_cuda_getDriverVersion\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mAssertionError\u001b[0m: Torch not compiled with CUDA enabled"
     ]
    }
   ],
   "source": [
    "t.cuda()\n",
    "t\n",
    "# 由于我的笔记本中未安装GPU版的pytorch，故会报错"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* GPU的运算速度比CPU快，为什么不直接在GPU上进行所有运算？ ——答: 将数据从CPU移到GPU代价高昂，如果任务本身较小较简单，这样做反而会让运算速度变慢；GPU对于能够分解成很多小任务的计算效果更好；\n",
    "\n",
    "* GPU计算堆栈的形式：GPU是底层硬件，Cuda是GPU的顶端软件框架，在Cuda的顶部是Cudnn；"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.张量(tensor)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1 张量介绍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 张量是神经网络中使用的主要数据结构，网络中的输入、输出和转换均使用张量表示；\n",
    "* n维张量中，n表示在结构中访问特定元素所需要的索引数量（张量是n维数组）；\n",
    "* 张量被称为一个泛化的原因：我们可以使用张量来表示所有的n维数组；"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 索引数量 | 计算机科学中的名称 | 数学中的名称 | Tensor表示|\n",
    "|:-------- |:------------------ |:------------ |:----------|\n",
    "|    0   |      数字      |    标量    |  0维张量  |\n",
    "|    1   |      数组      |    矢量    |  1维张量  |\n",
    "|    2   |    二维数组     |    矩阵    |  2维张量  |\n",
    "|    n   |    N维数组      |   N维张量   | n维张量  |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 注意：张量的维度跟我们所说的向两空间的向量维度不同；张量的维度并不能告诉我们张量中有多少个分量（如果我们在三维的欧几里得空间中有一个三维向量，即我们会有三个分量(x,y,z);而一个三维张量可以有多于三个分量，也可以有少于三个分量）；如下：二维张量t有9个分量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[1., 2., 3.],\n",
       "        [4., 5., 6.],\n",
       "        [7., 8., 9.]])"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = torch.Tensor([[1,2,3],[4,5,6],[7,8,9]])\n",
    "t"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 张量的属性(秩，轴，形状)\n",
    "* 张量的秩、轴和形状是使用张量时最关注的三个属性；\n",
    "* 所有的秩、轴和形状都与索引的概念有本质上的联系；"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.2.1 张量的秩\n",
    "* 张量的秩是指张量的维数\n",
    "* 一个张量的秩告诉我们需要多少个索引来访问或引用张量结构中包含的特定数据元素\n",
    "\n",
    "#### 2.2.2 张量的轴\n",
    "* 一个张量的轴是一个张量的一个特定维度\n",
    "* 对于张量，其最后一个轴的元素均为数字 \n",
    "* 张量的秩告诉我们一个张量有多少个轴\n",
    "\n",
    "#### 2.2.3 张量的形状\n",
    "* 张量的形状由每个轴的长度决定（知道了张量的形状就可知道每个轴的索引）\n",
    "* 张量的形状很重要：原因1：可以让我们从概念上想象一个张量(越高阶的张量越抽象)；原因2：形状可以提现所有有关轴、秩和索引的信息"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Tensor"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = torch.tensor([[1,2,3],[4,5,6],[7,8,9]])\n",
    "type(t)        # 查看张量的类型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([3, 3])"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t.shape    # 查看张量的形状；张量的shape和size是一样的"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rank = len(t.shape)        # 张量的秩等于其shape的长度\n",
    "rank"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[1],\n",
       "        [2],\n",
       "        [3],\n",
       "        [4],\n",
       "        [5],\n",
       "        [6],\n",
       "        [7],\n",
       "        [8],\n",
       "        [9]])"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t.reshape(9,1)      # 张量重塑会改变元素的分组，但不会改变元素本身，元素总数亦不会改变"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3 张量输入到神经网络"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* CNN输入张量的长度通常为4：[batchsize, color_channel, height, width]；通过这4个索引，可以在特定图像的特定颜色通道中导航到特定的像素；\n",
    "* 卷积神经网络的样本输入通常是批量的而不是单个的；\n",
    "* 张量经过卷积层后的变化：卷积会改变高度、宽度以及颜色通道的数量；通道数与滤波器的数量有关；滤波器的大小会影响到高度和宽度；\n",
    "* 经过卷积的通道不再叫彩色通道(已被改变)，而叫做特征通道(特征图:输入颜色通道和卷积滤波器所产生的卷积结果)\n",
    "* 输出通道 = 特征通道 = 特征映射"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.4 pytorch张量及其创建"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* pytorch神经网络中必须编写的第一行程序是“数据预处理程序”\n",
    "* 数据预处理的最终目标：是将我们要处理的任何数据转换成能够感知神经网络的张量"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.4.1 张量的属性\n",
    "| Data type          |   dtype     |   CPU tensor   |   GPU tensor  |\n",
    "|:-----------------------|:---------------|:-----------------|:---------------|\n",
    "| 32-bit floating point  | torch.float32 | torch.FloatTensor | torch.cuda.FloatTensor |\n",
    "| 64-bit floating point  | torch.float64 | torch.DoubleTensor| torch.cuda.DoubleTensor|\n",
    "| 16-bit floating point  | torch.float16 | torch.HalfTensor  | torch.cuda.HalfTensor |\n",
    "| 8-bit integer(unsigned)| torch.uint8  | torch.BYteTensor| torch.cuda.ByteTensor|\n",
    "| 8-bit integer(signed)  | torch.int8| torch.CharTensor| torch.cuda.CharTensor|\n",
    "| 16-bit integer(signed) | torch,int16|torch.ShortTensor|torch.cuda.ShortTensor|\n",
    "| 32-bit integer(signed) | torch.int32|torch.IntTensor| torch.cuda.IntTensor|\n",
    "| 64-bit integer(signed) | torch.int64|torch.LongTensor| torch.cuda.LongTensor|"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.int64\n",
      "cpu\n",
      "torch.strided\n"
     ]
    }
   ],
   "source": [
    "t = torch.tensor([[1,2,3],[4,5,6],[7,8,9]])\n",
    "print(t.dtype)        # 指定张量中包含的数据类型\n",
    "print(t.device)      # 指定数据分配的位置，CPU或GPU\n",
    "print(t.layout)    # 默认值strided：告诉我们张量数据是如何在内存中布局的，通常不需要改变"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 张量与张量之间的运算必须是相同数据类型在相同的设备上发生的"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.4.2 关于张量的两件事\n",
    "* 张量是包含一个同一类型的数据\n",
    "* 张量之间的计算依赖于类型和设备\n",
    "\n",
    "#### 2.4.3 用数据创建pytorch张量的4种方法"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[1., 2., 3.],\n",
      "        [4., 5., 6.],\n",
      "        [7., 8., 9.]])\n",
      "tensor([[1, 2, 3],\n",
      "        [4, 5, 6],\n",
      "        [7, 8, 9]], dtype=torch.int32)\n",
      "tensor([[1, 2, 3],\n",
      "        [4, 5, 6],\n",
      "        [7, 8, 9]], dtype=torch.int32)\n",
      "tensor([[1, 2, 3],\n",
      "        [4, 5, 6],\n",
      "        [7, 8, 9]], dtype=torch.int32)\n"
     ]
    }
   ],
   "source": [
    "t = np.array([[1,2,3],[4,5,6],[7,8,9]])\n",
    "print(torch.Tensor(t))           # 类构造函数\n",
    "print(torch.tensor(t))           # 工厂函数\n",
    "print(torch.as_tensor(t))        # 工厂函数\n",
    "print(torch.from_numpy(t))       # 工厂函数\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 工厂函数：接受参数输入并返回特定类型对象的函数；\n",
    "* 工厂函数允许更多的动态对象创建；具有更好的文档，并有更多的配置参数；\n",
    "* 通常情况下会更倾向于选择工厂函数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.4.4 无数据情况下创建张量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[1., 0., 0.],\n",
       "        [0., 1., 0.],\n",
       "        [0., 0., 1.]])"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 单位张量的创建(二维)\n",
    "torch.eye(3,3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[0., 0., 0.],\n",
       "        [0., 0., 0.],\n",
       "        [0., 0., 0.]])"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 全零张量的创建（二维）\n",
    "torch.zeros(3,3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[1., 1., 1.],\n",
       "        [1., 1., 1.],\n",
       "        [1., 1., 1.]])"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 全1张量的创建(二维)\n",
    "torch.ones(3,3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[0.0187, 0.5821, 0.6798],\n",
       "        [0.9749, 0.1791, 0.4112],\n",
       "        [0.5518, 0.6551, 0.5451]])"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 随机张量的创建(二维)\n",
    "torch.rand(3,3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.5 数据创建pytorch张量的四种方法的区别"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* pytorch张量是torch.tensor pytorch类的实例\n",
    "* 一个张量的抽象概念和一个pytorch张量的区别在于：pytorch张量给了我们一个具体的实现，我们可以使用和编码它"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.5.1 四种方法的区别1：数据类型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([1., 2., 3.])\n",
      "torch.float32\n",
      "tensor([1, 2, 3], dtype=torch.int32)\n",
      "torch.int32\n",
      "tensor([1, 2, 3], dtype=torch.int32)\n",
      "torch.int32\n",
      "tensor([1, 2, 3], dtype=torch.int32)\n",
      "torch.int32\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "import numpy as np\n",
    "\n",
    "data = np.array([1,2,3])\n",
    "t1 = torch.Tensor(data)\n",
    "print(t1)\n",
    "print(t1.dtype)\n",
    "t2 = torch.tensor(data)\n",
    "print(t2)\n",
    "print(t2.dtype)\n",
    "t3 = torch.as_tensor(data)\n",
    "print(t3)\n",
    "print(t3.dtype)\n",
    "t4 = torch.from_numpy(data)\n",
    "print(t4)\n",
    "print(t4.dtype)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 上述使用类构造函数和工厂函数后生成的数据类型不同，主要原因是：构造函数在构造一个张量时使用全局缺省值，而工厂函数通过输入数据的类型来推断输出数据的类型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function torch._C.get_default_dtype>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看全局缺省值的数据类型\n",
    "d = torch.get_default_dtype\n",
    "d"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([1., 2., 3.], dtype=torch.float64)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 工厂函数可以显示指定数据类型，类构造函数不能这样操作\n",
    "t = torch.tensor(np.array([1,2,3]), dtype=torch.float64)\n",
    "t"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.5.2 四种方法的区别2：数据分配内存方式"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1 2 3]\n",
      "tensor([1., 2., 3.])\n",
      "tensor([1, 2, 3], dtype=torch.int32)\n",
      "tensor([0, 0, 0], dtype=torch.int32)\n",
      "tensor([0, 0, 0], dtype=torch.int32)\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "import numpy as np\n",
    "\n",
    "data = np.array([1,2,3])\n",
    "print(data)\n",
    "\n",
    "t1 = torch.Tensor(data)\n",
    "t2 = torch.tensor(data)\n",
    "t3 = torch.as_tensor(data)\n",
    "t4 = torch.from_numpy(data)\n",
    "\n",
    "data[0] = 0\n",
    "data[1] = 0\n",
    "data[2] = 0\n",
    "# t1 和 t2 输出的都是更改前的数组\n",
    "print(t1)\n",
    "print(t2)\n",
    "# t3 和 t4 输出的都是更改后的数组\n",
    "print(t3)\n",
    "print(t4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 上述差异是由创建时分配内存的方式造成的：t1和t2的方式是将数组中的元素值直接拷贝到张量中，改变data中的元素值并不会影响到t1和t2中的值；t3和t4的方式是与data数组共享数据；（可将t1和t2的方式看作\"值传递\"；t3和t4的方式看作“地址传递”）\n",
    "|    共享数据    |    数据拷贝   |\n",
    "|:-----------------|:----------------|\n",
    "| torch.as_tensor() | torch.tensor()|\n",
    "| torch.from_numpy()| torch.Tensor()|\n",
    "* 由上可知numpy和tensor是数据共享的，所以他们可以无缝切换\n",
    "* 数据共享比数据拷贝更高效，更节省内存空间\n",
    "\n",
    "#### 2.5.3 最优的数据转换方法\n",
    "* 数据拷贝方式的最优选择是 torch.tensor() (因为是工厂函数)\n",
    "* 内存共享方式的最优选择是 torch.as_tensor() (因为torch.as_tensor可以接受任何python数据结构；而torch.from_numpy只接受numpy数组)\n",
    "* 数据拷贝的方式更注重实现；而内存共享的方式更注重代码性能\n",
    "\n",
    "#### 2.5.4 使用内存共享工厂函数的注意事项：\n",
    "* 1.由于numpy.ndaaray对象分配在CPU上，所以如果使用GPU的话，torch.as_tensor函数必须把数据从CPU上拷到GPU上\n",
    "* 2.as_tensor()对于python内置的数据结构，如列表，是无效的\n",
    "* 3.as_tensor的调用要求熟悉共享特征，以免对底层数据做不必要的更改，而影响到对象\n",
    "* 4.当as_tensor和numpy.ndarray有大量的相互往返的操作时，对性能的提升会有较大的影响"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.6 张量的重塑操作(reshaping)\n",
    "#### 2.6.1 常用张量操作类型\n",
    "* Reshaping operations\n",
    "* Element-wise operations\n",
    "* Reduction operations\n",
    "* Access operations\n",
    "#### 2.6.2 张量的重塑\n",
    "* 张量的重塑是最重要的张量操作：因为张量的形状能提供给我们一些具体的东西，我们可以用它来塑造和直观的理解张量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<built-in method size of Tensor object at 0x0000016A54552B88>\n",
      "torch.Size([3, 4])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "t = torch.tensor([[1,1,1,1],\n",
    "                 [2,2,2,2],\n",
    "                  [3,3,3,3]], dtype=torch.float32)\n",
    "print(t.size)\n",
    "print(t.shape)\n",
    "                  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor(12)"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 得到张量中的元素个数\n",
    "# 方法1：\n",
    "torch.tensor(t.shape).prod()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "12"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 方法2：\n",
    "t.numel()         # numel 是number of elements的简写"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 元素的个数与张量的重塑有直接的关系，重塑后每个轴长之积必须与元素个数相同"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.]])\n",
      "torch.Size([1, 12])\n",
      "tensor([1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.])\n",
      "torch.Size([12])\n",
      "tensor([[1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.]])\n",
      "torch.Size([1, 12])\n"
     ]
    }
   ],
   "source": [
    "# 张量的reshape,squeezing和unsqueezing\n",
    "print(t.reshape(1,12))\n",
    "print(t.reshape(1,12).shape)\n",
    "print(t.reshape(1,12).squeeze())\n",
    "print(t.reshape(1,12).squeeze().shape)\n",
    "print(t.reshape(1,12).squeeze().unsqueeze(dim=0))\n",
    "print(t.reshape(1,12).squeeze().unsqueeze(dim=0).shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* squeezing:可以移除所有长度为1的轴 (减少秩)\n",
    "* unsqueezing: 会增加一个长度为1的轴 （增加秩）\n",
    "* flatten张量：除去所有的轴，只保留一个，创造一个单轴张量包含原张量所有元素；\n",
    "* flatten操作是从一个卷积层过度到一个全连接层时在神经网络中必须发生的；\n",
    "* flatten操作是一种特殊的reshaping操作，即所有轴被挤压成一个轴"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.])\n"
     ]
    }
   ],
   "source": [
    "# 张量的flatten\n",
    "t = torch.tensor([[1,1,1,1],\n",
    "                 [2,2,2,2],\n",
    "                  [3,3,3,3]], dtype=torch.float32)\n",
    "# 方法1：\n",
    "def flatten(t):\n",
    "    t1 = t.reshape(1,-1)      # -1:根据输入t的总元素数确定该值为多少\n",
    "    t1 = t1.squeeze()\n",
    "    return t1\n",
    "print(flatten(t))    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.])\n"
     ]
    }
   ],
   "source": [
    "# 方法2：\n",
    "t2 = t.reshape(1,-1)[0]\n",
    "print(t2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.])"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 方法3：\n",
    "t3 = t.reshape(-1)\n",
    "t3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.])"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 方法4：\n",
    "t4 = t.view(t.numel())\n",
    "t4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.])"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 方法5：\n",
    "t5 = t.flatten()\n",
    "t5"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.7 多批次张量的flatten\n",
    "* flatten操作的前提：张量至少有两个轴\n",
    "* 全连接层接收被flatten的张量来作为输入\n",
    "* 多批次张量的flatten：保持batch的长度不变，只flatten图像通道部分"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([3, 4, 4])\n",
      "tensor([[[1, 1, 1, 1],\n",
      "         [1, 1, 1, 1],\n",
      "         [1, 1, 1, 1],\n",
      "         [1, 1, 1, 1]]])\n",
      "tensor([[1, 1, 1, 1],\n",
      "        [1, 1, 1, 1],\n",
      "        [1, 1, 1, 1],\n",
      "        [1, 1, 1, 1]])\n",
      "tensor([1, 1, 1, 1])\n",
      "tensor(1)\n",
      "torch.Size([3, 16])\n",
      "tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],\n",
      "        [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],\n",
      "        [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]])\n"
     ]
    }
   ],
   "source": [
    "# t1,t2,t3为三个4x4的图像\n",
    "t1 = torch.tensor([\n",
    "            [1,1,1,1],\n",
    "            [1,1,1,1],\n",
    "            [1,1,1,1],\n",
    "            [1,1,1,1]\n",
    "        ])\n",
    "\n",
    "t2 = torch.tensor([\n",
    "            [2,2,2,2],\n",
    "            [2,2,2,2],\n",
    "            [2,2,2,2],\n",
    "            [2,2,2,2]\n",
    "        ])\n",
    "\n",
    "t3 = torch.tensor([\n",
    "            [3,3,3,3],\n",
    "            [3,3,3,3],\n",
    "            [3,3,3,3],\n",
    "            [3,3,3,3]\n",
    "        ])\n",
    "# 使用stack将其合并成一个秩为3的张量\n",
    "t = torch.stack((t1,t2,t3))\n",
    "print(t.shape)    \n",
    "# 增加一个彩色通道轴，将其变成CNN期望的形式，以上三张图像均为灰度图像\n",
    "t = t.reshape(3,1,4,4)\n",
    "print(t[0])    # 第一个图像\n",
    "print(t[0][0])     # 第一个图像的第一个通道\n",
    "print(t[0][0][0])  # 第一个图像的第一个通道中的第一行像素\n",
    "print(t[0][0][0][0])   # 第一个图像的第一个通道中的第一行的第一个像素\n",
    "\n",
    "# 该张量的flatten:我们期望只将图像张量flatten，而不是全部flatten\n",
    "print(t.flatten(start_dim=1).shape)  \n",
    "print(t.flatten(start_dim=1))  # 参数start_dim告诉flatten函数应该从哪个轴开始"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.8 张量的操作\n",
    "#### 2.8.1 张量的元素运算(element-wise operation)\n",
    "* 元素运算是对张量元素的运算，这些元素在张量中对应或具有相同的位置索引\n",
    "* 两个张量必须有相同的形状才能执行元素操作"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "t1 = torch.tensor([\n",
    "                [1,2],\n",
    "                [3,4]\n",
    "            ], dtype=torch.float32)\n",
    "\n",
    "t2 = torch.tensor([\n",
    "                [9,8],\n",
    "                [7,6]\n",
    "            ], dtype=torch.float32)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[10., 10.],\n",
       "        [10., 10.]])"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t1 + t2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[3., 4.],\n",
       "        [5., 6.]])"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t1 +2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[-1.,  0.],\n",
       "        [ 1.,  2.]])"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t1 -2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[2., 4.],\n",
       "        [6., 8.]])"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t1 * 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[0.5000, 1.0000],\n",
       "        [1.5000, 2.0000]])"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t1 /2 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.8.2 张量的广播\n",
    "* 张量的广播：定义了在元素操作的过程中如何处理不同形状的张量，即：将标量变成与另一个张量相同的形状\n",
    "* 张量广播在数据标准化时会常用到"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[3, 5],\n",
      "        [3, 5]])\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "tensor([[3, 5],\n",
       "        [3, 5]])"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import torch\n",
    "import numpy as np\n",
    "t1 = torch.tensor([\n",
    "                [1,1],\n",
    "                [1,1]\n",
    "            ])\n",
    "t2 = torch.tensor([2,4])\n",
    "print(t1+t2)\n",
    "np.broadcast_to(t2.numpy,t1.shape) #广播\n",
    "t1 + t2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.8.3 张量的比较运算\n",
    "* 会返回一个与比较张量形状相同的张量，其值为0或1（0表示False；1表示True）\n",
    "* element-wise,component-wise以及point-wise的操作方式均相同"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.9 张量的缩减操作（reduction）\n",
    "* 缩减操作是一个减少张量中包含的元素数量的操作\n",
    "* 元素操作允许我们对多个张量进行操作；缩减操作允许我们对单个张量进行操作"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor(8.)\n",
      "9\n",
      "1\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "t = torch.tensor([\n",
    "                [0,1,0],\n",
    "                [2,0,2],\n",
    "                [0,3,0]\n",
    "            ], dtype = torch.float32)\n",
    "print(t.sum()) \n",
    "print(t.numel())  \n",
    "print(t.sum().numel()) \n",
    "print(t.sum().numel() < t.numel())  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "由上可知，求和操作是缩减操作，缩减操作还有：\n",
    "* t.sum()\n",
    "* t.prod()\n",
    "* t.mean()\n",
    "* t.std()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([6., 6., 6., 6.])"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 缩减操作通常允许跨数据结构计算总值\n",
    "# 对某个轴进行缩减操作\n",
    "t = torch.tensor([\n",
    "    [1,1,1,1],\n",
    "    [2,2,2,2],\n",
    "    [3,3,3,3]\n",
    "], dtype=torch.float32)\n",
    "t.sum(dim=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([ 4.,  8., 12.])"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t.sum(dim=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* argmax函数：得到张量的最大输出值的对应索引位置\n",
    "* 在实际应用中，经常在神经网络输出预测张量上使用argmax，确定哪个类别的预测最高"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor(5.)"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = torch.tensor([\n",
    "                [1,0,0,2],\n",
    "                [0,3,3,0],\n",
    "                [4,0,0,5]\n",
    "            ], dtype=torch.float32)\n",
    "t.max() "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor(11)"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t.argmax()            # 输出的是flatten后的索引"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
